// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Creates the Layer UX for the server side administration of
 * layers.
 *
 * @author jasonstredwick@google.com (Jason Stredwick)
 */


goog.provide('bite.layer.ServerAdmin');

goog.require('bite.common.events.DataBearingEvent');
goog.require('bite.common.mvc.helper');
goog.require('bite.common.net.xhr.async');
goog.require('bite.layer.AddUrl');
goog.require('bite.layer.Config');
goog.require('bite.layer.Constants');
goog.require('bite.layer.QuickConstruction');
goog.require('bite.layer.soy.Common');
goog.require('goog.Uri');
goog.require('goog.events');
goog.require('goog.json');



/**
 * Constructs an object that manages the layer UX.
 * @constructor
 * @export
 */
bite.layer.ServerAdmin = function() {
  /**
   * Whether or not the UI is ready.
   * @type {boolean}
   * @private
   */
  this.ready_ = false;
};
goog.addSingletonGetter(bite.layer.ServerAdmin);


/**
 * Initializes the Layer UX for ServerAdmin scripts.
 * @return {boolean} Returns whether or not the consoles are initialized.
 * @export
 */
bite.layer.ServerAdmin.prototype.init = function() {
  if (this.ready_) {
    return this.ready_;
  }

  // Initialize controllers, including empty string for base url.
  // TODO (jasonstredwick): Add some form of undoInit to clean up spare
  // <style> tags.
  if (bite.common.mvc.helper.initView(bite.layer.soy.Common.getView) &&
      bite.layer.AddUrl.getInstance().init('') &&
      bite.layer.Config.getInstance().init('') &&
      bite.layer.QuickConstruction.getInstance().init('')) {
    // Remove unused buttons.
    // TODO (jasonstredwick): This needs to happen elsewhere, clean up.
    var element = goog.dom.getElement(bite.layer.Config.Button.CLOSE);
    if (element) {
      goog.dom.removeNode(element);
    }

    this.setupConnections_();
    // TODO (jasonstredwick): Incorporate some form of waiting notification.
    this.startUI_();

    this.ready_ = true;
  }

  return this.ready_;
};


/**
 * Whether or not the view initialized.
 * @return {boolean} Returns whether or not he view is initialized.
 * @export
 */
bite.layer.ServerAdmin.prototype.isReady = function() {
  return this.ready_;
};


/**
 * Connects the UI functionality to each other and to the server.
 * @private
 */
bite.layer.ServerAdmin.prototype.setupConnections_ = function() {
  var addUrl = bite.layer.AddUrl.getInstance();
  var config = bite.layer.Config.getInstance();
  var construct = bite.layer.QuickConstruction.getInstance();

  // Connect AddUrl
  goog.events.listen(addUrl, bite.layer.AddUrlEventType.ADD,
      bite.layer.ServerAdmin.handleAddUrl_Add_);
  goog.events.listen(addUrl, bite.layer.AddUrlEventType.CANCEL,
      bite.layer.ServerAdmin.handleAddUrl_Cancel_);

  // Connect Config
  goog.events.listen(config, bite.layer.ConfigEventType.ADD,
      bite.layer.ServerAdmin.handleConfig_Add_);
  goog.events.listen(config, bite.layer.ConfigEventType.CREATE,
      bite.layer.ServerAdmin.handleConfig_Create_);
  goog.events.listen(config, bite.layer.ConfigEventType.EDIT,
      bite.layer.ServerAdmin.handleConfig_Edit_);
  goog.events.listen(config, bite.layer.ConfigEventType.REMOVE,
      bite.layer.ServerAdmin.handleConfig_Remove_);

  // Connect QuickConstruction
  goog.events.listen(construct, bite.layer.QuickConstructionEventType.CLOSE,
      bite.layer.ServerAdmin.handleQuickConstruction_Close_);
  goog.events.listen(construct, bite.layer.QuickConstructionEventType.RESET,
      bite.layer.ServerAdmin.handleQuickConstruction_Reset_);
  goog.events.listen(construct, bite.layer.QuickConstructionEventType.SAVE,
      bite.layer.ServerAdmin.handleQuickConstruction_Save_);
};


/**
 * Starts the layer configuration UI by retrieving the configuration data.
 * @private
 */
bite.layer.ServerAdmin.prototype.startUI_ = function() {
  var url = bite.layer.Constants.Commands.CONFIG_DATA;
  bite.common.net.xhr.async.get(url, goog.bind(this.startUIDone_, this));
};


/**
 * Once the configuration data is retrieved, enable the UI and bring up the
 * configuration console.
 * @param {boolean} success Whether or not the request was successful.
 * @param {string} data The data retrieved or an error message.
 * @private
 */
bite.layer.ServerAdmin.prototype.startUIDone_ = function(success, data) {
  try {
    var jsonData = /** @type {?Array} */ (goog.json.parse(data)) || [];
    bite.layer.Config.getInstance().reset(jsonData);
  } catch (error) {
    // TODO (jasonstredwick): Handle json.parse error.
  }

  // Enable the UX.
  bite.layer.ServerAdmin.getAddUrl_().enable();
  bite.layer.ServerAdmin.getConfig_().enable();
  bite.layer.ServerAdmin.getQuickConstruction_().enable();

  bite.layer.ServerAdmin.getConfig_().show();
  bite.layer.ServerAdmin.getConfig_().disable();
};


/**
 * Retrieves the AddUrl Console and converts it to an object in order to
 * execute its behaviors.
 * @return {Object} The console as a plain object.
 * @private
 */
bite.layer.ServerAdmin.getAddUrl_ = function() {
  return /** @type {Object} */ (bite.layer.AddUrl.getInstance());
};


/**
 * Retrieves the Config Console and converts it to an object in order to
 * execute its behaviors.
 * @return {Object} The console as a plain object.
 * @private
 */
bite.layer.ServerAdmin.getConfig_ = function() {
  return /** @type {Object} */ (bite.layer.Config.getInstance());
};


/**
 * Retrieves the QuickConstruction Console and converts it to an object in
 * order to execute its behaviors.
 * @return {Object} The console as a plain object.
 * @private
 */
bite.layer.ServerAdmin.getQuickConstruction_ = function() {
  return /** @type {Object} */ (bite.layer.QuickConstruction.getInstance());
};


/**
 * Handles the event when the Add button is pressed in the AddUrl console.
 * @param {bite.common.events.DataBearingEvent} event The event fired from the
 *     console.
 * @private
 */
bite.layer.ServerAdmin.handleAddUrl_Add_ = function(event) {
  // TODO (jasonstredwick): Add in some kind of indicator of processing.
  bite.layer.ServerAdmin.getAddUrl_().disable();

  var url = bite.layer.Constants.Commands.STATIC_ADD;
  var urlData = event.getData()[bite.layer.AddUrl.Data.URL];
  var data = {};
  data[bite.layer.AddUrl.Data.URL] = urlData;
  var queryData = goog.Uri.QueryData.createFromMap(data);

  bite.common.net.xhr.async.post(url, queryData.toString(),
      goog.partial(bite.layer.ServerAdmin.handleAddUrl_AddDone_, urlData));
};


/**
 * Post processing of sending the command to add a static layer url.
 * @param {string} url The static url added as a layer.
 * @param {boolean} success Whether or not the post was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleAddUrl_AddDone_ = function(url, success, data) {
  bite.layer.ServerAdmin.getAddUrl_().enable();

  if (!success || data == 'Already had url') {
    // TODO (jasonstredwick): Add in some way of indicating failure.  Currently
    // renabling the AddUrl console will indicate a failure.  If it was
    // successful then it would have closed the console and updated the
    // Config console.
    return;
  }

  var rowData = {};
  rowData[bite.layer.Config.Data.URL] = url;
  bite.layer.Config.getInstance().update(
      /** @type {{name: string, url: string}} */ (rowData));

  bite.layer.ServerAdmin.getAddUrl_().hide();
};


/**
 * Handles the event when the Cancel button is pressed in the AddUrl console.
 * @private
 */
bite.layer.ServerAdmin.handleAddUrl_Cancel_ = function() {
  bite.layer.ServerAdmin.getAddUrl_().hide();
};


/**
 * Handles the event when the Add Layer Url button is pressed on the Config
 * console.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_Add_ = function() {
  // TODO (jasonstredwick): Consider whether or not I want to reset the
  // input text box to nothing or leave the previous value.  Currently, it
  // leaves the previous value.
  bite.layer.ServerAdmin.getAddUrl_().show();
};


/**
 * Handles the event when the Create Layer button is pressed on the Config
 * console.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_Create_ = function() {
  bite.layer.QuickConstruction.getInstance().reset();
  bite.layer.ServerAdmin.getConfig_().hide();
  bite.layer.ServerAdmin.getQuickConstruction_().show();
  // TODO (jasonstredwick): Consider a way of indicating that data is being
  // loaded from the server.
  bite.layer.ServerAdmin.getQuickConstruction_().disable();

  var url = bite.layer.Constants.Commands.SIMPLE_DATA;
  bite.common.net.xhr.async.get(url,
      bite.layer.ServerAdmin.handleConfig_CreateDone_);
};


/**
 * Post processing of sending the command to create a simple layer url.
 * @param {boolean} success Whether or not the get was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_CreateDone_ = function(success, data) {
  bite.layer.ServerAdmin.getQuickConstruction_().enable();

  var defaultData = {};
  try {
    if (!success) {
      // TODO (jasonstredwick): Add in some way of indicating failure.
      // Currently, renabling the AddUrl console will indicate a failure.  If
      // it was successful then it would have closed the console and updated
      // the Config console.
      throw 'Request failed.';
    }

    defaultData = goog.json.parse(data);
  } catch (error) {
    // TODO (jasonstredwick): Figure out how to handle these kinds of errors.
    return;
  }

  defaultData[bite.layer.QuickConstruction.Data.EDIT] = 'true';
  defaultData = /** @type {{name: string, edit: string, ondemand: string,
                            state: string, bgJsCode: string, csJsCode: string,
                            cssCode: string}} */ (defaultData);
  bite.layer.QuickConstruction.getInstance().update(defaultData);
};


/**
 * Handles the event when the Create Layer button is pressed on the Config
 * console.
 * @param {bite.common.events.DataBearingEvent} event The event fired from the
 *     console.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_Edit_ = function(event) {
  bite.layer.QuickConstruction.getInstance().reset();
  bite.layer.ServerAdmin.getConfig_().hide();
  bite.layer.ServerAdmin.getQuickConstruction_().show();
  // TODO (jasonstredwick): Consider a way of indicating that data is being
  // loaded from the server.
  bite.layer.ServerAdmin.getQuickConstruction_().disable();

  var url = bite.layer.Constants.Commands.SIMPLE_DATA;
  var data = {};
  data[bite.layer.Config.Data.NAME] =
      event.getData()[bite.layer.Config.Data.NAME];
  var queryData = goog.Uri.QueryData.createFromMap(data);
  bite.common.net.xhr.async.post(url, queryData.toString(),
      bite.layer.ServerAdmin.handleConfig_EditDone_);
};


/**
 * Post processing of sending the command to edit a simple layer url.
 * @param {boolean} success Whether or not the get was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_EditDone_ = function(success, data) {
  bite.layer.ServerAdmin.getQuickConstruction_().enable();

  var layerData = {};
  try {
    if (!success) {
      // TODO (jasonstredwick): Add in some way of indicating failure.
      // Currently, renabling the AddUrl console will indicate a failure.  If
      // it was successful then it would have closed the console and updated
      // the Config console.
      throw 'Request failed.';
    }

    layerData = goog.json.parse(data);
  } catch (error) {
    // TODO (jasonstredwick): Figure out how to handle these kinds of errors.
    return;
  }

  layerData[bite.layer.QuickConstruction.Data.EDIT] =
      layerData[bite.layer.QuickConstruction.Data.NAME] ? 'false' : 'true';
  layerData = /** @type {{name: string, edit: string, ondemand: string,
                          state: string, bgJsCode: string, csJsCode: string,
                          cssCode: string}} */ (layerData);
  bite.layer.QuickConstruction.getInstance().update(layerData);
};


/**
 * Handles the event when the Remove button is pressed on the Config console.
 * @param {bite.common.events.DataBearingEvent} event The event fired from the
 *     console.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_Remove_ = function(event) {
  bite.layer.ServerAdmin.getConfig_().disable();
  // TODO (jasonstredwick): Add something say that loading is occurring.

  var eventData = event.getData();

  var url = bite.layer.Constants.Commands.CONFIG_REMOVE;
  var data = {};
  data[bite.layer.Config.Data.URL] =
      eventData[bite.layer.Config.Data.URL];
  var queryData = goog.Uri.QueryData.createFromMap(data);
  bite.common.net.xhr.async.post(url, queryData.toString(),
      goog.partial(bite.layer.ServerAdmin.handleConfig_RemoveDone_,
          event.getData()[bite.layer.Config.Data.URL],
          event.getData()[bite.layer.Config.Data.NAME]));
};


/**
 * Post processing of sending the command to remove a simple layer url.
 * @param {string} url The url of the layer.
 * @param {string|undefined} name An optional name of the layer.
 * @param {boolean} success Whether or not the get was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleConfig_RemoveDone_ =
    function(url, name, success, data) {
  bite.layer.ServerAdmin.getConfig_().enable();

  if (data != 'Removed') {
    // TODO (jasonstredwick): Handle the failure to remove layer.
    return;
  }

  bite.layer.Config.getInstance().remove(url, name);
};


/**
 * Handles the event when the Close button is pressed in the
 * QuickConstruction console.
 * @private
 */
bite.layer.ServerAdmin.handleQuickConstruction_Close_ = function() {
  bite.layer.ServerAdmin.getQuickConstruction_().hide();
  bite.layer.ServerAdmin.getConfig_().show();
};


/**
 * Handles the event when the Reset button is pressed in the
 * QuickConstruction console.
 * @private
 */
bite.layer.ServerAdmin.handleQuickConstruction_Reset_ = function() {
  bite.layer.QuickConstruction.getInstance().reset();
  // TODO (jasonstredwick): Consider a way of indicating that data is being
  // loaded from the server.
  bite.layer.ServerAdmin.getQuickConstruction_().disable();

  var url = bite.layer.Constants.Commands.SIMPLE_DATA;
  bite.common.net.xhr.async.get(url,
      bite.layer.ServerAdmin.handleQuickConstruction_ResetDone_);
};


/**
 * Handles the event when the Reset button is pressed in the
 * QuickConstruction console.
 * @param {boolean} success Whether or not the get was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleQuickConstruction_ResetDone_ =
    function(success, data) {
  bite.layer.ServerAdmin.getQuickConstruction_().enable();

  var defaultData = {};
  try {
    if (!success) {
      // TODO (jasonstredwick): Add in some way of indicating failure.
      // Currently, renabling the AddUrl console will indicate a failure.  If
      // it was successful then it would have closed the console and updated
      // the Config console.
      throw 'Request failed.';
    }

    defaultData = goog.json.parse(data);
  } catch (error) {
    // TODO (jasonstredwick): Figure out how to handle these kinds of errors.
    return;
  }

  defaultData[bite.layer.QuickConstruction.Data.EDIT] = 'true';
  defaultData = /** @type {{name: string, edit: string, ondemand: string,
                            state: string, bgJsCode: string, csJsCode: string,
                            cssCode: string}} */ (defaultData);
  bite.layer.QuickConstruction.getInstance().update(defaultData);
};


/**
 * Handles the event when the Save button is pressed in the
 * QuickConstruction console.
 * @param {bite.common.events.DataBearingEvent} event The event fired from the
 *     console.
 * @private
 */
bite.layer.ServerAdmin.handleQuickConstruction_Save_ = function(event) {
  // TODO (jasonstredwick): Add in some kind of indicator of processing.
  bite.layer.ServerAdmin.getQuickConstruction_().disable();

  var url = bite.layer.Constants.Commands.SIMPLE_UPDATE;
  var data = {'data': goog.json.serialize(event.getData())};
  var queryData = goog.Uri.QueryData.createFromMap(data);
  bite.common.net.xhr.async.post(url, queryData.toString(),
      goog.partial(bite.layer.ServerAdmin.handleQuickConstruction_SaveDone_,
                   event.getData()[bite.layer.QuickConstruction.Data.NAME]));
};


/**
 * Post processing of sending the command to reset a simple layer url.
 * @param {string} name The name of the layer updated.
 * @param {boolean} success Whether or not the get was successful.
 * @param {string} data The response or an error message if request failed.
 * @private
 */
bite.layer.ServerAdmin.handleQuickConstruction_SaveDone_ =
    function(name, success, data) {
  // TODO (jasonstredwick): Add some way to indicate success.
  bite.layer.ServerAdmin.getAddUrl_().enable();

  if (!success || data == 'Failed') {
    // TODO (jasonstredwick): Add in some way of indicating failure.
    // Currently, renabling the AddUrl console will indicate a failure.  If it
    // was successful then it would have closed the console and updated the
    // Config console.
    return;
  }

  var rowData = {};
  rowData[bite.layer.Config.Data.URL] = data;
  rowData[bite.layer.Config.Data.NAME] = name;
  bite.layer.Config.getInstance().update(
      /** @type {{name: string, url: string}} */ (rowData));
};

